#pragma once

#include <DXFile.h>
#include "HelperStructs.h"
#include "DynArray.h"



namespace Renderer
{
	struct FrameEx : public D3DXFRAME
	{
		D3DXMATRIX toRoot;
	};

	class Graphics;

	class Mesh
	{
	private:
		Graphics * graphics;

		int numMeshes;
		sMesh* meshes;
		ID3DXMesh * mesh;


		int numFrames;
		sFrame* frames;

		D3DXFRAME * rootFrame;
		ID3DXAnimationController * animationController;
		ID3DXSkinInfo * skinInfo;

		DWORD numBones;
		DWORD maxVertInfluences;

		DynArray<D3DXMATRIX> transformMatrices;
		DynArray<D3DXMATRIX *> combinedMatrices;

		AABB	boundingBox;
		BoundingSphere boundingSphere;

		void ParseXFileData(ID3DXFileData * data, sFrame * parentFrame, char * texturePath);
		void MapFramesToBones(sFrame * frame);
		D3DXFRAME* Mesh::FindNodeWithMesh(D3DXFRAME* frame);

		void BuildMesh(ID3DXMesh* mesh);

	public:
		Mesh();
		~Mesh();

		bool IsLoaded();

		int     GetNumFrames();
		sFrame* GetParentFrame();
		sFrame* GetFrame(char * name);

		int     GetNumMeshes();
		sMesh*  GetParentMesh();
		sMesh*  GetMesh(char * name);

		void GetBounds(float * minX, float * minY, float * minZ, float * maxX, float * maxY, float * maxZ, float * radius);

		bool Load(Graphics * graphics, char * fileName, char * texturePath = ".\\");
		void Free();
	};


		struct sXFileRotateKey
	{
		DWORD time;
		DWORD floats;
		float w, x, y, z;
	};

	struct sXFileScaleKey
	{
		DWORD time;
		DWORD floats;
		D3DXVECTOR3 scale;
	};

	struct sXFilePositionKey
	{
		DWORD time;
		DWORD floats;
		D3DXVECTOR3 pos;
	};

	struct sXFileMatrixKey
	{
		DWORD time;
		DWORD floats;
		D3DXMATRIX matrix;
	};

	struct sRotateKey
	{
		DWORD time;
		D3DXQUATERNION quaternion;
	};

	struct sPositionKey
	{
		DWORD time;
		D3DXVECTOR3 Pos;
		D3DXVECTOR3 PosInterpolation;
	};

	struct sScaleKey
	{
		DWORD time;
		D3DXVECTOR3 scale;
		D3DXVECTOR3 scaleInterpolation;
	};

	struct sMatrixKey
	{
		DWORD time;
		D3DXMATRIX matrix;
		D3DXMATRIX interpolationMatrix;
	};

	struct sAnimation
	{
		char * name;
		char * frameName;
		sFrame * frame;

		bool loop;
		bool linear;

		DWORD numPositionKeys;
		sPositionKey * positionKeys;

		DWORD numRotateKeys;
		sRotateKey * rotateKeys;

		DWORD numScaleKeys;
		sScaleKey * scaleKeys;

		DWORD numMatrixKeys;
		sMatrixKey * matrixKeys;

		sAnimation * next;

		sAnimation() : 
		name(NULL),
			frameName(NULL),
			frame(NULL),
			loop(false),
			linear(true),
			numPositionKeys(0),
			numRotateKeys(0),
			numScaleKeys(0),
			numMatrixKeys(0),
			positionKeys(NULL),
			rotateKeys(NULL),
			scaleKeys(NULL),
			matrixKeys(NULL),
			next(NULL)
		{
		}

		~sAnimation()
		{
			delete [] name;
			delete [] frameName;
			delete [] positionKeys;
			delete [] rotateKeys;
			delete [] scaleKeys;
			delete [] matrixKeys;
			delete next;
		}

		void Update(DWORD time, bool smooth)
		{
			ULONG key;
			DWORD timeDiff, intTime;
			D3DXMATRIX matrix, tempMatrix;
			D3DXVECTOR3 vector;
			D3DXQUATERNION quat;

			if(frame == NULL)
				return;

			if(numRotateKeys != 0 || numScaleKeys != 0 || numPositionKeys != 0)
			{
				D3DXMatrixIdentity(&matrix);

				if(rotateKeys != NULL)
				{
					key = 0;
					for(DWORD i = 0; i < numRotateKeys; ++i)
					{
						if(rotateKeys[i].time <= time)
							key = i;
						else
							break;
					}

					if(key == (numRotateKeys - 1) || smooth == false)
					{	
						quat = rotateKeys[key].quaternion;
					}
					else
					{
						timeDiff = rotateKeys[key + 1].time - rotateKeys[key].time;
						intTime = time - rotateKeys[key].time;

						D3DXQuaternionSlerp(&quat, &rotateKeys[key].quaternion, &rotateKeys[key + 1].quaternion, ((float)intTime / (float)timeDiff));
					}

					D3DXMatrixRotationQuaternion(&tempMatrix, &quat);
					D3DXMatrixMultiply(&matrix, &matrix, &tempMatrix);
				}

				if(scaleKeys != NULL)
				{
					key = 0;
					for(DWORD i = 0; i < numScaleKeys; ++i)
					{
						if(scaleKeys[i].time <= time)
							key = i;
						else
							break;
					}

					if(key == (numScaleKeys - 1) || smooth == false)
					{
						vector = scaleKeys[key].scale;
					}
					else
					{
						intTime = time - scaleKeys[key].time;

						vector = scaleKeys[key].scale + scaleKeys[key].scaleInterpolation * (float)intTime;
					}

					D3DXMatrixScaling(&tempMatrix, vector.x, vector.y, vector.z);
					D3DXMatrixMultiply(&matrix, &matrix, &tempMatrix);
				}

				if(positionKeys != NULL)
				{
					key = 0;
					for(DWORD i = 0; i < numPositionKeys; ++i)
					{
						if(positionKeys[i].time <= time)
						{
							key = i;
						}
						else
							break;
					}

					if(key == (numPositionKeys - 1) || smooth == false)
					{
						vector = positionKeys[key].Pos;
					}
					else
					{
						intTime = time - positionKeys[key].time;

						vector = positionKeys[key].Pos + positionKeys[key].PosInterpolation * (float)intTime;
					}

					D3DXMatrixTranslation(&tempMatrix, vector.x, vector.y, vector.z);
					D3DXMatrixMultiply(&matrix, &matrix, &tempMatrix);
				}
				frame->transformedMatrix = matrix;
			}

			if(numMatrixKeys != 0 && matrixKeys != NULL)
			{
				key = 0;

				for(DWORD i = 0; i < numMatrixKeys;++i)
				{
					if(matrixKeys[i].time <= time)
						key = i;
					else
						break;
				}

				if(key == (numMatrixKeys - 1) || smooth == false)
				{
					frame->transformedMatrix = matrixKeys[key].matrix;
				}
				else
				{
					intTime = time - matrixKeys[key].time;

					matrix = matrixKeys[key].interpolationMatrix * (float)intTime;
					frame->transformedMatrix = matrix + matrixKeys[key].matrix;
				}
			}
		}
	};

	struct sAnimationSet
	{
		char * name;
		sAnimation * animation;

		ULONG length;
		sAnimationSet * next;

		sAnimationSet() : name(NULL), animation(NULL), length(0), next(NULL)
		{
		}

		~sAnimationSet()
		{
			delete [] name;
			delete animation;
			delete next;
		}
		
		sAnimationSet * FindSet(char * name)
		{
			sAnimationSet * set;

			if(name == NULL)
				return this;

			if(this->name != NULL && strcmp(name, this->name) == 0)
			 return this;

			if(next != NULL)
			{
				set = next->FindSet(name);
				if(set != NULL)
					return set;
			}

			return NULL;
		}

		void Update(DWORD time, bool smooth)
		{
			sAnimation * anim;

			anim = animation;
			while(anim != NULL)
			{
				if(length == 0)
					anim->Update(0, false);
				else
				{
					if(time >= length && anim->loop == false)
						anim->Update(time, false);
					else
						anim->Update((time % length), smooth);
				}

				anim = anim->next;
			}
		}

	};

	class Mesh;

	class Animation
	{
	protected:
		long numAnimations;
		sAnimationSet * animationSet;

		void ParseXFileData(ID3DXFileData * data, sAnimationSet * parentAnim, sAnimation * currentAnim);

	public:
		Animation();
		~Animation();

		bool IsLoaded();

		int GetNumAnimations();
		sAnimationSet * GetAnimationSet(char * name = NULL);
		ULONG GetLength(char * name = NULL);

		bool Load(char * fileName, Mesh * mapMesh = NULL);
		void Free();

		bool MapToMesh(Mesh * Mesh);
		bool SetLoop(bool toLoop, char * name = NULL);
	};

}